<?php
// $Id: user_channels_service.php 300 2008-10-10 14:00:31Z hugo.wetterberg $

define('USER_CHANNELS_MAP_SUBSCRIPTION', 1);
define('USER_CHANNELS_NEWS_SUBSCRIPTION', 2);

/**
 * Implementation of hook_service
 *
 * @return void
 **/
function user_channels_service() {
  return array(
    array(
      '#method'   => 'user_channels.get_favorites',
      '#callback' => 'user_channels_get_favorites_service',
      '#args'     => array(
        array(
          '#name'         => 'type',
          '#type'         => 'int',
          '#optional'     => True,
          '#description'  => t('The type of favorites to get')),
        array(
          '#name'         => 'uid',
          '#type'         => 'int',
          '#optional'     => True,
          '#description'  => t('The user to fetch favorites for, current user if omitted')),
        ),
      '#return'   => 'struct',
      '#help'     => t('Returns a users favorite channels'),
    ),
    array(
      '#method'   => 'user_channels.add_favorite',
      '#callback' => 'user_channels_add_favorite_service',
      '#args'     => array(
        array(
          '#name'         => 'feed',
          '#type'         => 'string',
          '#description'  => t('Feed path')),
        array(
          '#name'         => 'title',
          '#type'         => 'string',
          '#description'  => t('Favorite name')),
        array(
          '#name'         => 'filters',
          '#type'         => 'array',
          '#description'  => t('The filters that are included in the subscription')),
        array(
          '#name'         => 'status',
          '#type'         => 'int',
          '#description'  => t('The status of the subscription')),
        array(
          '#name'         => 'uid',
          '#type'         => 'int',
          '#optional'     => True,
          '#description'  => t('The user to add the favorite for, current user if omitted')),
      ),
      '#return'   => 'int',
      '#help'     => t('Adds a favorite for the current user'),
    ),
    array(
      '#method'   => 'user_channels.remove_favorite',
      '#callback' => 'user_channels_remove_favorite_service',
      '#args'     => array(
        array(
          '#name'         => 'feed',
          '#type'         => 'string',
          '#description'  => t('Feed path')),
        array(
          '#name'         => 'uid',
          '#type'         => 'int',
          '#optional'     => True,
          '#description'  => t('The user to remove the favorite from, current user if omitted')),
      ),
      '#return'   => 'int',
      '#help'     => t('Removes a favorite from the current user'),
    ),
    array(
      '#method'   => 'user_channels.feed_search',
      '#callback' => 'user_channels_feed_search',
      '#args'     => array(
        array(
          '#name'         => 'query',
          '#type'         => 'string',
          '#description'  => t('The search query')),
        array(
          '#name'         => 'module',
          '#type'         => 'string',
          '#optional'     => True,
          '#description'  => t('Optional parameter to restrict feed search to a module')),
        array(
          '#name'         => 'feed_type',
          '#type'         => 'string',
          '#optional'     => True,
          '#description'  => t('Optional parameter to restrict feed search to a feed type')),
      ),
      '#return'   => 'struct',
      '#help'     => t('Service for searching through feeds'),
    ),
    array(
      '#method'   => 'user_channels.feed_map_items',
      '#callback' => 'user_channels_feed_map_items',
      '#args'     => array(
        array(
          '#name'         => 'feeds',
          '#type'         => 'array',
          '#description'  => t('An array with feeds to fetch map items for')),
        array(
          '#name'         => 'filters',
          '#type'         => 'array',
          '#optional'     => TRUE,
          '#description'  => t('Optional global filters to set as default for the feeds')),
        array(
          '#name'         => 'fields',
          '#type'         => 'array',
          '#optional'     => TRUE, 
          '#description'  => t('A list of fields to return.')),
        array(
          '#name'         => 'offset',
          '#type'         => 'int',
          '#optional'     => TRUE, 
          '#description'  => t('The offset to use if a subset is to be fetched')),
        array(
          '#name'         => 'length',
          '#type'         => 'int',
          '#optional'     => TRUE, 
          '#description'  => t('The length to use if a subset is to be fetched')),
      ),
      '#return'   => 'struct',
      '#help'     => t('Service for fetching map items for feeds'),
    ),
    array(
      '#method'   => 'user_channels.feed_news_items',
      '#callback' => 'user_channels_feed_news_items',
      '#args'     => array(
        array(
          '#name'         => 'feeds',
          '#type'         => 'array',
          '#description'  => t('An array with feeds to fetch news items for')),
        array(
          '#name'         => 'filters',
          '#type'         => 'array',
          '#optional'     => TRUE,
          '#description'  => t('Optional global filters to set as default for the feeds')),
        array(
          '#name'         => 'fields',
          '#type'         => 'array',
          '#optional'     => TRUE, 
          '#description'  => t('A list of fields to return.')),
        array(
          '#name'         => 'offset',
          '#type'         => 'int',
          '#optional'     => TRUE, 
          '#description'  => t('The offset to use if a subset is to be fetched')),
        array(
          '#name'         => 'length',
          '#type'         => 'int',
          '#optional'     => TRUE, 
          '#description'  => t('The length to use if a subset is to be fetched')),
      ),
      '#return'   => 'struct',
      '#help'     => t('Service for fetching map items for feeds'),
    ),
  );
}

function user_channels_channel_item_filters() {
  return array(
    'promoted' => array(
      'conditions' => 'user_channels_promoted_filter_conditions',
    ),
    'type' => array(
      'conditions' => 'user_channels_type_filter_conditions'
    ),
    'published_after' => array(
      'conditions' => 'user_channels_published_after_filter_conditions'
    ),
  );
}

function user_channels_promoted_filter_conditions($promoted) {
  return array("AND n.promote=%d", ($promoted?1:0));
}

function user_channels_type_filter_conditions($type) {
  return array("AND n.type='%s'", $type);
}

function user_channels_published_after_filter_conditions($time) {
  return array("AND n.created >= %d", $time);
}

/**
 * Access callback function that determines if the current user
 * has read access to a users favorites
 *
 * @param int $uid The uid of the user whose favorites are going to be read
 *
 * @return bool
 **/
function user_channels_favorites_access_read($uid) {
  global $user;
  return user_access('access everyones user channel favorites') || ($uid == $user->uid && user_access('access own user channel favorites'));
}

/**
 * Access callback function that determines if the current user
 * has write access to a users favorites
 *
 * @param int $uid The uid of the user whose favorites are going to be changed
 *
 * @return bool
 **/
function user_channels_favorites_access_write($uid) {
  global $user;
  return user_access('edit everyones user channel favorites') || ($uid == $user->uid && user_access('edit own user channel favorites'));
}

function user_channels_get_favorites_service($type=Null, $uid=Null) {
  global $user;
  if (!isset($uid)) {
    $uid = $user->uid;
  }
  return user_channels_get_favorites($uid, $type);
}

function user_channels_add_favorite_service($feed, $title, $filters, $status, $uid=Null) {
  global $user;
  if (!isset($uid)) {
    $uid = $user->uid;
  }
  
  if (user_channels_favorites_access_write($uid)) {
    $parts = explode('/', $feed);
    return user_channels_add_favorite($uid, $parts[0], $parts[1], $parts[2], $title, $filters, $status);
  }
  else {
    throw new Exception(t('You don\'t have access to edit the favorites of the user with the id @id', array('@id'=>$uid)));
  }
}

function user_channels_remove_favorite_service($feed, $uid=Null) {
  global $user;
  if (!isset($uid)) {
    $uid = $user->uid;
  }
  
  if (user_channels_favorites_access_write($uid)) {
    $parts = explode('/', $feed);
    return user_channels_remove_favorite($uid, $parts[0], $parts[1], $parts[2]);
  }
  else {
    throw new Exception(t('You don\'t have access to edit the favorites of the user with the id @id', array('@id'=>$uid)));
  }
}

/**
 * Gets feeds matching a set of criteria
 *
 * @param string $query Part of the name of the feed
 * @param string $module Optional name of a module to restrict the search to
 * @param string $module Optional name of a feed_type to restrict the search to
 *
 * @return array An array of channels
 **/
function user_channels_feed_search($query, $module=Null, $feed_type=Null) {
  //Don't run empty queries
  if(empty($query)) {
    return array();
  }
  
  $feeds = array();
  $channels = user_channels_channel_info($module);
  
  foreach ($channels as $module => $channel_info) {
    if (isset($feed_type)) { //Only search inside a specific feed type
      if (isset($channel_info[$feed_type]) && isset($channel_info[$feed_type]['feeds'])) {
        $res = call_user_func($channel_info[$feed_type]['feeds'], $query);
        $path = $module .'/'. $feed_type .'/';
        foreach($res as $id => $title) {
          $feeds[] = array($path . $id, $title);
        }
      }
    }
    else { //Search inside all feed types
      foreach ($channel_info as $feed_type => $feed_def) { 
        if (isset($feed_def['feeds'])) {
          $res = call_user_func($feed_def['feeds'], $query);
          $path = $module .'/'. $feed_type .'/';
          foreach($res as $id => $title) {
            $feeds[] = array($path . $id, $title);
          }
        }
      }
    }
  }
  
  return $feeds;
}

/**
 * Gets the map items for a collection of feeds
 *
 * @param array $feeds 
 * An associative array with feeds where the feed path is the key 
 * and any feed specific filters are set as values, or FALSE if no
 * feed-specific filters should be used.
 *
 * @param array $default_filters 
 * Optional array with filters that should be applied on all feeds. Might be
 * overridden by feed-specific filters
 *
 * @param array $fields
 * Optional array with the names of the attributes on the node that should be loaded
 * All attributes will be returned if omitted
 *
 * @return array An array of results keyed after feed path
 **/
function user_channels_feed_map_items($feeds, $default_filters=Null, $fields=Null, $offset=0, $length=0) {
  if (!isset($default_filters)) {
    $default_filters = array();
  }
  
  //Only show items with a position
  $default_filters['has_position'] = true;
  
  //Make sure that we have a position field
  $position_field = user_channels_position_field();
  if (!isset($fields)) {
    $fields = array($position_field);
  }
  else {
    if(!in_array($position_field, $fields)) {
      $fields[] = $position_field;
    }
  }
  
  return _user_channels_feed_items('map_items', $feeds, $default_filters, $fields, $offset, $length);
}

/**
 * Gets the news items for a collection of feeds
 *
 * @param array $feeds 
 * An associative array with feeds where the feed path is the key 
 * and any feed specific filters are set as values, or FALSE if no
 * feed-specific filters should be used.
 *
 * @param array $default_filters 
 * Optional array with filters that should be applied on all feeds. Might be
 * overridden by feed-specific filters
 *
 * @param array $fields
 * Optional array with the names of the attributes on the node that should be loaded
 * All attributes will be returned if omitted
 *
 * @return array An array of results
 **/
function user_channels_feed_news_items($feeds, $default_filters=Null, $fields=Null, $offset=0, $length=0) {
  if (!isset($default_filters)) {
    $default_filters = array();
  }
  
  //Make sure that we have a created field
  if(isset($fields) && !in_array('created', $fields)) {
    $fields[] = 'created';
  }
  
  return _user_channels_feed_items('news_items', $feeds, $default_filters, $fields, $offset, $length);
}

/**
 * Utility class for sorting an array based on
 * a value at a specific index
 **/
class UserChannelItemSorter {
  function UserChannelItemSorter($created_idx) {
    $this->created_idx = $created_idx;
  }
  
  function compare($a, $b) {
    return $b[$this->created_idx]-$a[$this->created_idx];
  }
}

/**
  * Gets the items for a collection of feeds
  *
  * @param string $handler
  * The handler that should be called to generate the items
  *
  * @param array $feeds 
  * An associative array with feeds where the feed path is the key 
  * and any feed specific filters are set as values, or FALSE if no
  * feed-specific filters should be used.
  *
  * @param array $default_filters 
  * Optional array with filters that should be applied on all feeds. Might be
  * overridden by feed-specific filters
  *
  * @param array $fields
  * Optional array with the names of the attributes on the node that should be loaded
  * All attributes will be returned if omitted
  *
  * @return array An array of results keyed after feed path
 **/
function _user_channels_feed_items($handler_name, $feeds, $default_filters=Null, $fields=Null, $offset=0, $length=0) {
  $channels = user_channels_channel_info();
  $items = array();
  
  if (!isset($default_filters)) {
    $default_filters = array();
  }
  else {
    user_channels_alter_filters($default_filters);
  }
  
  $cache_calls = array();
  $call_paths = array();
  
  //This is where we queue up our calls to
  //the cache function
  foreach ($feeds as $path => $filters) {
    $parts = explode('/', $path);
    //Check that we can handle the feed
    if (isset($channels[$parts[0]]) && //Module declares feeds
        isset($channels[$parts[0]][$parts[1]]) && //and module has feed type
        isset($channels[$parts[0]][$parts[1]][$handler_name])) { //and handler exists
      $feed_info = $channels[$parts[0]][$parts[1]];
      $handler = $feed_info[$handler_name]['handler'];
      //Check that the defined handler is callable
      if (is_callable($handler)) {
        $f = user_channels_filter_merge($default_filters, user_channels_alter_filters($filters));
        $cache_calls[] = array($handler, array(intval($parts[2]), $f, $fields));
        $call_paths[] = $path;
      }
    }
  }
  
  // Run it through the cache function
  $eids = array();
  $call_count = count($cache_calls);
  if ($call_count) {
    $results = preemptive_cache_multiple($cache_calls);
    for ($i=0; $i<$call_count; $i++) {
      foreach($results[$i] as $entry) {
        if (!isset($eids[$entry[0]])) { // Don't add items twice
          $entry[] = $call_paths[$i]; // Keep track of the source feed
          $items[] = $entry;
          $eids[$entry[0]] = true;
        }
      }
    }
  }
  
  //Sort the items if it's news
  if($handler_name=='news_items') {
    //Keep track of the index of the created field
    $created_idx = array_search('created', $fields);
    
    $s = new UserChannelItemSorter($created_idx);
    usort($items, array($s,'compare'));
    $count = count($items);

    if($length || $offset) {
      $items = array_slice($items, $offset, $length);
    }
  }
  
  return array(
    'items' => $items,
    'count' => $count,
    'offset' => $offset,
  );
}